
import java.util.*;

class Principal {

    private static final long tempoMaximo = 10000,  tempoMinimo = 5;
    private static final int repeticions = 1000,  anchura = 18;
    private static boolean bucle = false;
    private static Crono crono = new Crono();
    private static int filas;
    private static int nInicial = 125;
    private static int salto = 2;

    private static void imprimeLina(int i) {
        for (int j = 0; j < i; j++) {
            System.out.print("-");
        }
        System.out.println();
    }

    /**
    A funcion 'avaliar' devolve un array de 'double' que conten os tempos empregados polo algoritmo
    de ordeacion para ordear distintas cantidades de elementos (125,250,500,...). A disposicion
    inicial dos elementos dentro do array ven dada polo parametro 'ini'.
    NOTA: Os tempos que precisen empregar un bucle apareceran como negativos, para diferencialos
    dos tempos que non o precisan
     **/
    private static double[] avaliar(Ordenacion orde, InicializacionDeVectores ini) {

        filas = 15; // En principio, faranse 'filas' iteracions (se non se supera o tempo maximo)

        double[] tempo = new double[15];
        Entero[] v;

        for (int n = nInicial,  indice = 0; indice < filas; n *= salto, indice++) {
            v = new Entero[n];
            ini.inicializar(v);

            //* Tempos para ORDEACION *//
            crono.iniciar();
            orde.ordenar(v);
            tempo[indice] = crono.tiempo();

            //* ORDEACION para tempos menores a 'tempoMinimo' *//
            if (tempo[indice] < tempoMinimo) {

                crono.iniciar();
                for (int j = 0; j < repeticions; j++) {
                    ini.inicializar(v);
                    orde.ordenar(v);
                }
                long aux1 = crono.tiempo();

                crono.iniciar();
                for (int j = 0; j < repeticions; j++) {
                    ini.inicializar(v);
                }
                long aux2 = crono.tiempo();
                tempo[indice] = -(aux1 - aux2) / (double) repeticions;
            }

            if (Math.abs(tempo[indice]) > tempoMaximo) {
                filas = indice;
                break;
            }
        }

        return (tempo);
    }


    /**
    A funcion 'analise' encargase de avalia-las complexidades de cada unha das ordenacions, en distintos
    casos: o mellor(ascendente), o intermedio(aleatoria) e o peor(descendente).
    Para elo recibe o tipo de ordeacion('orde'), o tipo de inicializacion('ini'), o array de tempos obtidos
    coa funcion 'avaliar' (tempo) e un umbral (no caso de tratarse da Ordeacion Rapida, noutro caso o valor
    de 'umbral' sera ignorado)
     **/
    private static void analise(Ordenacion orde, InicializacionDeVectores ini, double[] tempo) {

        boolean eAleatoria = ini instanceof Aleatoria;
        boolean eAscendente = ini instanceof Ascendente;
        boolean eDescendente = ini instanceof Descendente;

        System.out.println();
        System.out.println("\nAlgoritmo de " + orde.toString() + " con " + ini.toString() + ".\n");
        System.out.print(Format.format("n", anchura - 10, true));
        System.out.print(Format.format("T(n)", anchura - 5, false));
        System.out.print(Format.format("T(n)/n", anchura, false));
        System.out.print(Format.format("T(n)/(n*log(n))", anchura, false));
        System.out.println(Format.format("T(n)/n^1.5", anchura, false));

        imprimeLina(5 * anchura - 15);

        for (int n = nInicial,  indice = 0; indice < filas; n *= salto, indice++) {
            System.out.print(Format.format(String.valueOf(n), anchura - 10, true));
            System.out.print(Format.format(Math.abs(tempo[indice]), anchura - 6, anchura - 14) + ((tempo[indice] <= 0) ? '*' : ' '));

            tempo[indice] = Math.abs(tempo[indice]);

            System.out.print(Format.format(tempo[indice]/n, anchura, anchura - 7));
            System.out.print(Format.format(tempo[indice]/(n*Math.log(n)), anchura, anchura - 7));
            System.out.println(Format.format(tempo[indice]/Math.pow(n, 1.5), anchura, anchura - 7));
        }
        imprimeLina(5 * anchura - 15);
        System.out.println("(*) Empregouse un bucle de " + repeticions + " iteracions. O tempo reflectido corresponde a unha iteracion");
        System.out.println("Todolos tempos en milisegundos");
    }

    private static void test(Ordenacion o) {

        Entero[] v = new Entero[10];
        (new Descendente()).inicializar(v);

        for (int i = 0; i < v.length; i++) {
            System.out.print(v[i] + " ");
        }
        System.out.println();

        o.ordenar(v);

        for (int i = 0; i < v.length; i++) {
            System.out.print(v[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Ordenacion ordeacion;

        Ascendente iniAscend = new Ascendente();
        Aleatoria iniAleat = new Aleatoria();
        Descendente iniDescend = new Descendente();

//        test(new OrdenacionMonticulos());

        ordeacion = new OrdenacionMonticulos();
        analise(ordeacion, iniAscend, avaliar(ordeacion, iniAscend));
        analise(ordeacion, iniAleat, avaliar(ordeacion, iniAleat));
        analise(ordeacion, iniDescend, avaliar(ordeacion, iniDescend));



        System.out.println("\n\nOs algoritmos executaronse sobre unha arquitectura " + System.getProperty("os.arch") +
                           " baixo " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
                           ".\nCon Java " + System.getProperty("java.version"));
        System.out.println("Data: " + (new Date()).toString());
    }
}
